package com.my.geojson.test.wfs;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import com.github.rholder.retry.RetryException;
import com.github.rholder.retry.Retryer;
import com.my.geojson.test.wfs.exception.GeoXmlQlException;
import com.my.geojson.test.wfs.utils.BtoaEncode;
import com.my.geojson.test.wfs.utils.XMLUtils;
import com.my.geojson.test.wfs.xmlGenerate.WFSTXmlGenerate;
import com.my.geojson.test.wfs.xmlGenerate.impl.InsertWfsXmlGenerate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.geotools.data.*;
import org.geotools.data.postgis.PostgisNGDataStoreFactory;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.feature.AttributeTypeBuilder;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.referencing.CRS;
import org.opengis.feature.Feature;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.ExecutionException;


@Slf4j
@Service
public class GeoToolsManager {

    public static final int CREATE = 0;
    public static final int APPEND = 1;
    public static final int OVERWRITE = 2;

    private static CoordinateReferenceSystem crs4490;

    @Autowired
    private Retryer<Boolean> retryer;
    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;

    public GeoToolsManager() {
        try {
            crs4490 = CRS.decode("EPSG:4490");
        } catch (FactoryException e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据数据库连接参数，获取数据源
     *
     * @return
     * @throws IOException
     */
    private DataStore getPostgisDataStore(String host, String port, String user, String password, String database, String schema) throws IOException {
        Map<String, Object> params = new HashMap<>();
        params.put("dbtype", "postgis");
        params.put("host", host);
        params.put("port", port);
        params.put("schema", schema);
        params.put("database", database);
        params.put("user", user);
        params.put("passwd", password);
        params.put(PostgisNGDataStoreFactory.LOOSEBBOX.key, true);
        /**
         * 开启下面后测试数据（7000+要素）由原来的19秒加快到13秒
         */
        params.put(PostgisNGDataStoreFactory.PREPARED_STATEMENTS.key, true);
        System.out.println("连接数据库");
        return DataStoreFinder.getDataStore(params);
    }

    public boolean shp2postgis(String shppath, String host, String port, String user, String password, String database, String schema, String tablename, String encode, int option) {
        // 首先尝试连接数据库
        DataStore pgDatastore = null;
        try {
            pgDatastore = getPostgisDataStore(host, port, user, password, database, schema);
        } catch (IOException e) {
            e.printStackTrace();
            System.err.println("连接数据库失败");
            return false;
        }

        // 尝试读取shp
        ShapefileDataStore shpDataStore = null;
        try {
            shpDataStore = createShpDataStore(shppath, encode);
        } catch (IOException e) {
            e.printStackTrace();
            System.err.println("读取shp失败");
            return false;
        } catch (FactoryException e) {
            e.printStackTrace();
            System.err.println("读取shp失败");
            return false;
        }

        // 写入数据
        return writePostGIS(pgDatastore, shpDataStore, tablename, option);
    }

    /**
     * 根据shp文件创建数据源
     *
     * @param shppath
     * @param encode
     * @return
     * @throws IOException
     * @throws FactoryException
     */
    private ShapefileDataStore createShpDataStore(String shppath, String encode) throws IOException, FactoryException {
        ShapefileDataStore shapefileDataStore = new ShapefileDataStore(new File(shppath).toURI().toURL());
        // 根据用户指定的字符集编码设置解析编码
        shapefileDataStore.setCharset(Charset.forName(encode));
        // 如果没有指定坐标系，则强制使用4490
        CoordinateReferenceSystem crs = shapefileDataStore.getSchema().getCoordinateReferenceSystem();
        if (null == crs) {
            crs = crs4490;
            shapefileDataStore.forceSchemaCRS(crs);
        }
        // 根据数据源获取数据类型
        return shapefileDataStore;
    }

    /**
     * 根据数据源获取要素集合的迭代器
     *
     * @param dataStore
     * @return
     * @throws IOException
     */
    private FeatureIterator getIterator(ShapefileDataStore dataStore) throws IOException {
        // 获取shp数据源
        FeatureSource featureSource = dataStore.getFeatureSource();
        FeatureCollection featureCollection = featureSource.getFeatures();
        return featureCollection.features();
    }

    /**
     * 根据一个shp要素类型创建一个新的要素类型
     *
     * @param tablename
     * @param shpfeaturetype
     * @return
     */
    private SimpleFeatureType createPostGISType(String tablename, SimpleFeatureType shpfeaturetype) {
        SimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder();
        // 这些需要设置下默认空间字段，否则不会识别
        typeBuilder.setDefaultGeometry("geom");
        // 这里设置一下名称，当shp文件名是大写时需要换成小写
        typeBuilder.setName(tablename);
        // 设置坐标系，单独设置没有起到作用
        CoordinateReferenceSystem crs = shpfeaturetype.getCoordinateReferenceSystem();
        typeBuilder.setCRS(crs);
        // 新创建一个属性描述集合
        List<AttributeDescriptor> attributeDescriptors = new ArrayList<>();
        for (AttributeDescriptor attributeDescriptor : shpfeaturetype.getAttributeDescriptors()) {
            //属性构造器
            AttributeTypeBuilder build = new AttributeTypeBuilder();
            build.init(attributeDescriptor.getType());
            build.setNillable(true);
            //获取字段名,改为小写
            String name = StringUtils.isNotEmpty(attributeDescriptor.getLocalName()) ? attributeDescriptor.getLocalName().toLowerCase() : attributeDescriptor.getLocalName();
            if (attributeDescriptor instanceof GeometryDescriptor) {
                //修改空间字段名
                name = "geom";
            }
            //设置字段名
            build.setName(name);
            //创建新的属性类
            AttributeDescriptor descriptor = build.buildDescriptor(name, attributeDescriptor.getType());
            attributeDescriptors.add(descriptor);
        }
        typeBuilder.addAll(attributeDescriptors);
        return typeBuilder.buildFeatureType();
    }

    /**
     * 将shp数据源写入postgis数据源
     *
     * @param pgDatastore
     * @param shpDataStore
     * @param tablename
     * @param option
     * @return
     * @throws IOException
     */
    private boolean writePostGIS(DataStore pgDatastore, ShapefileDataStore shpDataStore, String tablename, int option) {
        SimpleFeatureType shpfeaturetype = null;
        try {
            shpfeaturetype = shpDataStore.getSchema();
        } catch (IOException e) {
            e.printStackTrace();
            System.err.println("shp读取失败");
            return false;
        }
        // 原shp数据源的迭代器
        FeatureIterator iterator = null;
        try {
            iterator = getIterator(shpDataStore);
        } catch (IOException e) {
            e.printStackTrace();
            System.err.println("shp读取失败");
            return false;
        }

        // 新建一个postgis要素类型构造器
        SimpleFeatureType postGISType = this.createPostGISType(tablename, shpfeaturetype);
        // 新建一个事务
        Transaction transaction = new DefaultTransaction();
        // 新建一个要素写对象
        FeatureWriter<SimpleFeatureType, SimpleFeature> featureWriter = null;
        try {
            // 表操作
            switch (option) {
                case CREATE:
                    pgDatastore.createSchema(postGISType);
                    System.out.println("postgis创建数据表成功");
                    break;
                case APPEND:
                    break;
                case OVERWRITE:
                    pgDatastore.removeSchema(tablename);
                    System.out.println("postgis删除数据表成功");
                    pgDatastore.createSchema(postGISType);
                    System.out.println("postgis创建数据表成功");
                    break;
                default:
                    System.err.println("非法操作");
                    return false;
            }
            // 要素操作
            featureWriter = pgDatastore.getFeatureWriterAppend(tablename, transaction);
            System.out.println("开始写入");
            Date startTime = new Date();
            // 定义循环用到的变量
            Feature feature = null;
            SimpleFeature simpleFeature = null;
            Collection<Property> properties = null;
            Property property = null;
            String pname = null;
            Object pvalue = null;
            while (iterator.hasNext()) {
                feature = iterator.next();
                simpleFeature = featureWriter.next();
                properties = feature.getProperties();
                Iterator<Property> propertyIterator = properties.iterator();
                while (propertyIterator.hasNext()) {
                    property = propertyIterator.next();
                    pname = property.getName().toString().toLowerCase();
                    if ("the_geom".equals(pname)) {
                        pname = "geom";
                    }
                    // 如果值是空字符串则换成Null,表示无值
                    pvalue = property.getValue();
                    if (null == pvalue || (null != pvalue && StringUtils.isEmpty(property.getValue().toString()))) {
                        pvalue = null;
                    }
                    simpleFeature.setAttribute(pname, pvalue);
                }
                featureWriter.write();
            }
            // 最后提交事务，可以加快导入速度
            transaction.commit();
            Date endTime = new Date();
            System.out.println("写入结束,共耗时：" + (endTime.getTime() - startTime.getTime()));
            System.out.println("shp导入postgis成功");
            return true;
        } catch (IOException e) {
            try {
                transaction.rollback();
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
            return false;
        } finally {
            try {
                transaction.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (null != iterator) {
                iterator.close();
            }
            if (null != featureWriter) {
                try {
                    featureWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            shpDataStore.dispose();
            pgDatastore.dispose();
        }
    }


    private HttpHeaders createAuthorHeader(String username, String password) {
//        HttpHeaders headers = RestUtil.getHeaderApplicationJson();
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization",
                "Basic " +
                        BtoaEncode.botaEncodePassword(username + ":" + password));
        return headers;
    }

    @Value("${geoserver.wfs.error.path:D:\\tmp\\error.xml}")
    private String path;

    private String xmlString = null;
    /**
     *
     * @param path 输出的地址
     * @param content 写入的内容
     */
    private void writeFile2Disk(String path,String content){
        File file = new File(path);
        if (!file.getParentFile().exists()) {
            boolean result = file.getParentFile().mkdirs();
        }
        FileOutputStream fos= null;
        OutputStreamWriter osw= null;
        BufferedWriter  bw= null;
        try {
            fos = new FileOutputStream(file);
            osw = new OutputStreamWriter(fos, "UTF-8");
            bw = new BufferedWriter(osw);
            bw.write(content);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("=====> 写入error xml 失败",e);
        } finally {
            //注意关闭的先后顺序，先打开的后关闭，后打开的先关闭
            try {
                if(bw!=null){
                    bw.close();
                }
                if(osw!=null){
                    osw.close();
                }
                if(fos!=null){
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
                log.error("file流关闭失败,",e);
            }
        }


    }

    /**
     *
     * @param ip
     * @param port
     * @param username
     * @param password
     * @param typeName
     * @param variables 要插入的参数
     * @param params 要进行修改的数据唯一编号
     * @return
     * @throws Exception
     */
    public void insertUpdateByGeoWfs(String businessId,String updateUser,String ip, String port, String username, String password, String typeName, JSONObject variables, Map<String, Object> params){
        taskExecutor.execute(()->{
            try {
                retryer.call(()->{
                    try {
                        this.send2GeoWfs(businessId,updateUser,ip, port, username, password, typeName, variables, params,
                                InsertWfsXmlGenerate.class);
                        //成功则释放xmlString
                        this.xmlString = null;
                        return true;
                    }catch (GeoXmlQlException e){
                        log.error("请求失败,{}",e);
                        return false;
                    }
                });
            } catch (ExecutionException e) {
                e.printStackTrace();
                this.writeFile2Disk(this.path,this.xmlString);
                log.error("线程错误",e);
            } catch (RetryException e) {
                e.printStackTrace();
                log.error("重试错误",e);
                this.writeFile2Disk(this.path,this.xmlString);
            }
        });
    }

    private JSONObject send2GeoWfs(String businessId,String updateUser,String ip, String port, String username, String password, String typeName, JSONObject variables, Map<String, Object> params, Class<? extends WFSTXmlGenerate> c) throws Exception {
        HttpHeaders headers = this.createAuthorHeader(username, password);
        WFSTXmlGenerate generate = c.newInstance();
        Document document = generate.createXml(businessId,updateUser,typeName,
                "http://" + ip + ":" + port + "/geoserver/wfs",
                variables, params);
//        log.info(document.asXML());
        this.xmlString = document.asXML();
        String url = "http://" + ip + ":" + port + "/geoserver/wfs";

        HttpResponse response = HttpRequest.post(url)
                .header(headers)//头信息，多个头信息多次调用此方法即可
                .body(document.asXML())//表单内容
                .timeout(20000)//超时，毫秒
                .execute();
//        ResponseEntity<String> response =
//                HttpUtil.get()
//                RestUtil.request(,
//                HttpMethod.POST, headers, null,, String.class);
//        if(response.getStatusCode().is4xxClientError()){
//            throw new GeoXmlQlException("host无法请求!");
//        }
//        if(response.getStatusCode().is5xxServerError()){
//            throw new GeoXmlQlException("系统错误!");
//        }
        log.info(response.body());
        int status = response.getStatus();
        if(200==status && response.body().contains("SUCCESS")){
            return XMLUtils.xmltoJson(response.body());
        }
        throw new GeoXmlQlException("请求错误!");
    }

}


